package com.le.tester.imooc.oldfairy.jvm;

import javassist.*;
import org.junit.Test;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;

/**
 * createTime：2023/6/29 14:13
 * description：MyClassLoader
 */
public class MyClassLoader {

    private static byte[] genClass() {

        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("greetings.Go");

        CtMethod ctMethod = new CtMethod(CtClass.voidType, "greetings", new CtClass[]{}, ctClass);

        ctMethod.setModifiers(Modifier.PUBLIC);
        try {
            ctMethod.setBody("{ System.out.println(\"Hi, greetings!\"); }");
            ctClass.addMethod(ctMethod);
        } catch (CannotCompileException e) {
            e.printStackTrace();
        }

        try {
            return ctClass.toBytecode();
        } catch (IOException | CannotCompileException e) {
            e.printStackTrace();
        }
        return null;
    }

    class BinLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            if (name.equalsIgnoreCase("greetings.Go")) {
                byte[] genClass = genClass();
                int len = 0;
                if (Objects.nonNull(genClass)) {
                    len = genClass.length;
                }
                return defineClass("greetings.Go", genClass, 0, len);
            }
            return super.findClass(name);
        }
    }

    class NetLoader extends ClassLoader {
        byte[] bytes;

        public NetLoader() throws IOException {
            this.connect();
        }

        private void connect() throws IOException {
            try (Socket socket = new Socket("localhost", 8000)) {
                bytes = this.toByteArray(socket.getInputStream());
            }
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            if (name == "greetings.Go") {
                return defineClass("greetings.Go", bytes, 0, bytes.length);
            }
            return super.findClass(name);
        }


        public byte[] toByteArray(InputStream input) throws IOException {
            try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024 * 8];
                int n;
                while (-1 != (n = input.read(buffer))) {
                    output.write(buffer, 0, n);
                }
                return output.toByteArray();
            }
        }
    }

    @Test
    public void testGen() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        BinLoader loader = new BinLoader();

        Class<?> clazz = loader.loadClass("greetings.Go");

        Object go = clazz.getConstructor().newInstance();

        go.getClass().getMethod("greetings").invoke(go);
    }

    @Test
    public void server() throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        byte[] bytes = genClass();
        while (true) {
            try (Socket clientSocket = serverSocket.accept()) {
                System.out.println("received request...");
                OutputStream out = clientSocket.getOutputStream();
                out.write(bytes);
                out.flush();
            }
        }
    }
    @Test
    public void test_net() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {

        NetLoader loader = new NetLoader();

        Class<?> clazz = loader.loadClass("greetings.Go");

        Object go = clazz.getConstructor().newInstance();

        go.getClass().getMethod("greetings").invoke(go);
    }

}
